/*
 Creator: Kisamin Resident and copyright by her.  All rights reserved except those specifically set forth here:
 You may:
 	 Use these scripts in any project
	 Modify these scripts in any way you wish subject to:
	 		1. Retaining this set of comments
			2. Explicity commenting and showing any changes you might make
 You may NOT:
 	 License these scripts to someone else with restrictions differing from the above.
	 Charge a fee for the scripts themselves (although you may charge for an object that
	 		utilizes these scripts to add functionality)
	 Charge a fee for any "library" or "collection" of scripts that includes this script.

If you have questions about your rights under this coment and/or the Gnu CopyLeft, please request further information
and/or explicit permission from Kisamin Resident
*/

float	k_fMissileLife = 10.0;		// Lifetime of the missile in seconds.

float	k_fMissileVelocity = 1.;	// Speed of the missile (average)
float	k_fMissileAgility = 180.0;	// Amount of heading change per second that the missile can do - in degrees!!!!

float	k_fSeekerViewWidth = 180.0;		// The angle that the seeker can see in degrees.  180 is a half-sphere ahead.
float	k_fSeekerViewDepth = 20.0;		// The distance the seeker can see in meters.
float	k_fSeekerPingInterval = 0.05;	// The delay between sensor pings

integer k_iIgnoreOwner = FALSE;			// Should we ignore the owner in our collisions and sensors?
integer k_iIgnoreGroup = FALSE;			// Should we ignore things owned by our group in collisions and sensors?
integer k_iRequireSit  = FALSE;         // Should we require the target be sitting?

/*
	==================================================================================
	No changing anything below this line!
	==================================================================================
*/

integer	k_iCmdAnnounce	= 10000;			// Command code for link messages
integer	k_iCmdExplode	= 10001;			// Tell the warhead to explode
integer k_iCmdNewTarget = 10002;			// We've locked on to a new victim
integer k_iCmdSelfDestruct = 10003;			// We need to self -destruct either due to time or some other function.
integer	k_iCmdKill = 11000;					// Tell a damage script to kill someone

float	k_fNoSensorTravelDistance = 60.0;	// How far ahead to set the movement target if the seeker does not see anything.  It will RARELY get this far before another sensor sweep adjusts the target.  Must be less than 64 meters.
float	g_fNoSensorTravelTime;				// How long it will take to travel the above distance.

float	g_fMissileAgility;					// The calculated turning rate in radians.
float	g_fSeekerViewWidth;					// The calculated 1/2 field width used by an llSensorRepeat call.

key		g_kVictim;							// Who are we chasing currently?  Used to tell if we should send notice that we've switched targets.

// The following are for debugging and can/should be removed for production code.
integer	g_iLastMessageType = 0;
integer k_iLastMessageTypeNoSensor = 1;
integer k_iLastMessageTypeSensor = 2;

// Start guiding in on a specified target.  This needs to be called from a routine
// that sets up the llDetect_____ functions such as sensor or collision_start
// pragma inline

doFollow (integer in_iItemNumber) {
	
	//
	// If we are passed no target then we should travel straight ahead and continue to look for a target.
	//
	if (in_iItemNumber == -1) {
    	vector l_vTravelOffset = <k_fNoSensorTravelDistance, 0., 0.> * llGetRootRotation ();
    	vector l_vTravelDestination = llGetRootPosition () + l_vTravelOffset;
    	llMoveToTarget (l_vTravelDestination, g_fNoSensorTravelTime);
    	return;
	}
	
	// We have a target to follow.  We get some values and do a few calculations
	// so that we don't have to do it multiple times in this routine.
	vector l_vMyPosition = llGetRootPosition ();
	vector l_vTargetPosition = llDetectedPos (in_iItemNumber);
	vector l_vOffsetToTarget = l_vTargetPosition - l_vMyPosition;
	float l_fTimeToTarget = llVecDist (l_vMyPosition, l_vTargetPosition) / k_fMissileVelocity;
	
	// Because of the inherent limits of llMoveToTarget, we should not have the time less than
	// 0.044444444 seconds.  I use 0.05 to give us a little bit of a safety margin.  So we need
	// to ensure that l_fTimeToTarget is not less than that value.
	if (l_fTimeToTarget < 0.05)
		l_fTimeToTarget = 0.05;
		
	llMoveToTarget (l_vTargetPosition, l_fTimeToTarget);
	
	// Although the missile is perfectly happy sliding sideways or even backwards to the target, this
	// looks strange AND has effects on the sensor.  Thus we need to start turning towards the target.
	
	// Calculate what our actual direction would be if we were facing directly at the target right now.
	rotation l_rRotationToTarget = llRotBetween (<1.0, 0., 0.>, l_vOffsetToTarget);
	
	// How much of a turn from our current direction is that?
	float l_fTurnAngle = llAngleBetween (llGetRootRotation (), l_rRotationToTarget);
	llMessageLinked (LINK_SET, k_iCmdAnnounce, "angle to target: " + (string)(l_fTurnAngle * RAD_TO_DEG), llGetOwner ());
	
	// How long will that turn take, based on our missile's agility?
	float l_fTimeToTurn = l_fTurnAngle / g_fMissileAgility;
	
	// We institute the same limits on time as we did for llMoveToTarget - i.e. a minimum of .05 seconds
	// to make the turn.  In addition, it must be a positive number of seconds.
	if (l_fTimeToTurn < 0.)
		l_fTimeToTurn = -l_fTimeToTurn;
	if (l_fTimeToTurn < 0.05)
		l_fTimeToTurn = 0.05;

	// Now perform the turn.
	llRotLookAt (l_rRotationToTarget, 1., l_fTimeToTurn);
	
	// And if this is a new target, then announce the new targeting lock.
    if (g_kVictim != llDetectedKey (in_iItemNumber)) {
		g_kVictim = llDetectedKey (in_iItemNumber);
		llMessageLinked (LINK_SET, k_iCmdNewTarget, "", g_kVictim);
	}
}



default {
    state_entry() {
    	// Precalculate as much as we can so we're not calculating it in-flight.
    	g_fMissileAgility = k_fMissileAgility * DEG_TO_RAD;
    	g_fSeekerViewWidth = k_fSeekerViewWidth * DEG_TO_RAD / 2.0;
    	g_fNoSensorTravelTime = k_fNoSensorTravelDistance / k_fMissileVelocity;
    	if (g_fNoSensorTravelTime < 0.05)
    		g_fNoSensorTravelTime = 0.05;
    	llSetBuoyancy (1.0);
    	llSetStatus (STATUS_DIE_AT_EDGE, TRUE);
    }
    
    on_rez (integer in_iParam) {
    	if (in_iParam) {
    		// We want to do as little work as possible here, so we pre-calculate in state_entry
    		// as much as we can
    		llVolumeDetect (TRUE);
    		llSensorRepeat ("",						// Set the seeker head to working.
    						NULL_KEY,
    						AGENT_BY_LEGACY_NAME,
    						k_fSeekerViewDepth,
    						g_fSeekerViewWidth,
    						k_fSeekerPingInterval);
			llSetTimerEvent (k_fMissileLife);
    	} else {
    		// We weren't rezzed from a launcher.  Dont' arm the seeker head or ignite the motor.
    		llSetStatus (STATUS_PHANTOM, TRUE);
    		llSensorRemove ();
    		llMessageLinked (LINK_SET, k_iCmdAnnounce, "Touch me to arm me.  Touch me again to disarm me.", llGetOwner ());
    	}
    }
    
    // Toggle the temp on rez status IF we are touched by our owner
    touch_start (integer in_iNumTouchers) {
    	if (llDetectedKey (0) == llGetOwner ()) {
    		list l_lPrimParams = llGetPrimitiveParams ([PRIM_TEMP_ON_REZ]);
    		integer l_iNewValue = ! llList2Integer (l_lPrimParams, 0);
    		llSetPrimitiveParams ([PRIM_TEMP_ON_REZ, l_iNewValue]);
    		llSetStatus (STATUS_PHYSICS, l_iNewValue);
    		string l_sMessage = "Temp on rez and physics set to ";
    		if (l_iNewValue)
    			l_sMessage += "TRUE";
    		else
    			l_sMessage += "FALSE";
    		llMessageLinked (LINK_THIS, k_iCmdAnnounce, l_sMessage, llGetOwner ());
    	}
    }
    
    // There are no avatars in that are both in front of us and are within range of the sensor
    // So we move straight ahead and hope that something comes into view.
    no_sensor () {
    	if (g_iLastMessageType != k_iLastMessageTypeNoSensor) {
    		llMessageLinked (LINK_SET, k_iCmdAnnounce, "entering no_sensor.", NULL_KEY);
    		g_iLastMessageType = k_iLastMessageTypeNoSensor;
    	}
    	g_kVictim = NULL_KEY;
    	doFollow (-1);
    }
    
    // Although we have mechanism for setting temp on rez, there is also a guard timer
    // to self destruct after a reasonable amount of time that is independent of the
    // warhead.
    timer () {
    	llMessageLinked (LINK_SET, k_iCmdAnnounce, "Entering timer.", llGetOwner ());
    	llSetTimerEvent (0.);
    	llSensorRemove ();
    	llSleep (1.0);
    	llMessageLinked (LINK_SET, k_iCmdSelfDestruct, "", NULL_KEY);
    	llSleep (5.0);
    	llDie ();
    }
    
    
    // There is at least one avatar within the seeker head parameters
    sensor (integer in_iNumSensed) {
	  if (g_iLastMessageType != k_iLastMessageTypeSensor) {
		llMessageLinked (LINK_SET, k_iCmdAnnounce, "Entering sensor.", llGetOwner ());
		g_iLastMessageType = k_iLastMessageTypeSensor;
	  }
    	
	  integer i;
	  
	  if (k_iIgnoreGroup || k_iIgnoreOwner) {
		for (i=0; i<in_iNumSensed; i++)
		  if ((k_iIgnoreGroup || ! llDetectedGroup (i))
			  && (k_iIgnoreOwner || (llDetectedKey (i) != llGetOwner ()))
			  && (!k_iRequireSitting || (llGetAgentInfo (llDetectedKey (i)) & AGENT_ON_OBJECT))) {
			doFollow (i);
			return;
		  }
		doFollow (-1);
	  } else
		doFollow (0);
    }    	
}
